#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<mutex>
#include<algorithm>
#include"../comm/httplib.h"
#include<jsoncpp/json/json.h>
#include<fstream>
#include"../comm/util.hpp"
#include"../comm/log.hpp"
#include"oj_model.hpp"
#include"oj_view.hpp"
#include"oj_view.hpp"
namespace ns_control
{
using namespace ns_log;
using namespace ns_util;
using namespace ns_model;
using namespace ns_view;
class Machine
{
public:
std::string ip;//ip
int port;      //port
uint64_t load; //load
std::mutex *mtx;//mutex禁止拷贝
public:
Machine():ip(""),port(0),load(0),mtx(nullptr)
{}
~Machine()
{}
void IncLoad()//提升负载
{
  if(mtx)mtx->lock();
  ++load;
  if(mtx)mtx->unlock();
}
void DecLoad()//减少负载
{
   if(mtx)mtx->lock();
  --load;
  if(mtx)mtx->unlock();
}
void ResetLoad()
  {
    if(mtx)mtx->lock();
    load=0;
    if(mtx)mtx->unlock();
  }
uint64_t Load()
{
   uint64_t  _load=0;
   if(mtx)mtx->lock();
    _load=load;
  if(mtx)mtx->unlock();
  return _load;
}
};
const std::string sevice_mechine="./conf/service_machine.conf";
class LoadBlance
{
    //主机下标id
 private:std::vector<Machine> machines;
 //在线主机id
 std::vector<int> online;
 //离线主机id
 std::vector<int> offline;
 std::mutex mtx;
 public: 
 LoadBlance(){
    assert(LoadConf(sevice_mechine));
 }
 ~LoadBlance(){}
 public:
  bool LoadConf(const std::string &machine_conf)
  {
     std::ifstream in(machine_conf);
     if(!in.is_open())
     {
        LOG(FATAL)<<"加载: "<<machine_conf<<"失败"<<"\n";
        return false;
     }
     std::string line;
     while(std::getline(in,line))
     {std::vector<std::string>tokens;
       StringUtil::SplitString(line,&tokens,":");
       if(tokens.size()!=2)
       {
        LOG(WARNING)<<"切分"<<line<<"失败"<<"\n";
        continue;
       }
       Machine m;
       m.ip=tokens[0];
       m.port=atoi(tokens[1].c_str());
       m.load=0;
       m.mtx=new std::mutex();
       online.push_back(machines.size());
      machines.push_back(m);
     }
     in.close();
     return true;

  }
  bool SmartChoice(int *id,Machine **m)
  {
    //使用选择好的主机(更新负载)
    mtx.lock();
    int online_num=online.size();
    if(online_num==0)
    {
      LOG(FATAL)<<"所有后端编译主机已经离线"<<"\n";
      return false;
    }
    uint64_t min_load=machines[online[0]].Load();
    *id=online[0];
    *m=&machines[online[0]];
    for(int i=1;i<online_num;i++)
    {
      uint64_t temp_load=machines[online[i]].Load();
       if(min_load>temp_load)
       {
         min_load=temp_load;
         *id=online[i];
         *m=&machines[online[i]];
       }
    }
    mtx.unlock();
    return true;
  }
  
  void OfflineMachine(int which)
  {
    mtx.lock();
    for(auto iter=online.begin();iter!=online.end();iter++)
    {
      if(*iter==which)
      {
        machines[which].ResetLoad();
        online.erase(iter);
        offline.push_back(which);
        break;
      }
    }
    mtx.unlock();
  }
  void OnlineMachine()
  {
     mtx.lock();
    online.insert(online.end(),offline.begin(),offline.end());
    offline.erase(offline.begin(),offline.end());
    mtx.unlock();
    LOG(INFO)<<"所有主机已上线"<<"\n";
  }
  void ShowMachines()
  {
    mtx.unlock();
    std::cout<<"在线主机: ";
    for(auto &id:online)
    {
       std::cout<<id<<" ";
    }
    std::cout<<"\n";
     std::cout<<"离线主机: ";
    for(auto &id:offline)
    {
       std::cout<<id<<" ";
    }
    std::cout<<"\n";
    mtx.lock();

  }

};





    class Control
    {
        private:
        Model model_;
        View view_;
        LoadBlance load_blance_;
        public:
        Control()
        {}

        ~Control()
        {}
        public:
         void RecoveryMachine()
        {
          load_blance_.OnlineMachine();
        }
       bool  AllQuestions(string *html)
       {  vector<struct Question> all;
            bool ret=true;;
           if(model_.GetAllQuestions(&all))
           {
            //获取题目信息,构建网页
           sort(all.begin(),all.end(),[](const struct Question &q1,const struct Question &q2){
              return atoi(q1.num.c_str())<atoi(q2.num.c_str());
            });
            view_.AllExpandHtml(all,html)
;           }
           else
           {
             *html="获取题目失败,形成题目列表失败";
             ret=false;
           }
           return ret;
       }
       bool Question(const string &num,string *html)
       {
        struct Question q;
        bool ret=true;
           if(model_.GetOneQuestion(num,&q))
           {
                view_.OneExpandHtml(q,html);
           }
           else
           {
            *html="题目"+num+"不存在";
            ret=false;
           }
           return ret;
       } //id
        //code
        //input
       void Judge(const std::string &num,const std::string in_json,std::string *out_json)
       {
        LOG(DEBUG)<<in_json<<"\nnum:"<<num<<"\n";
         struct Question q;
         model_.GetOneQuestion(num,&q);
       //in_json反序列化->getid
         Json::Reader reader;
         Json::Value in_value;
         reader.parse(in_json,in_value);
         std::string code=in_value["code"].asString();
       //拼接代码
         Json::Value compile_value;
         compile_value["input"]=in_value["input"].asString();
         compile_value["code"]=code+q.tail;  
         compile_value["cpu_limit"]=q.cpu_limit;
         compile_value["mem_limit"]=q.mem_limit;
         Json::FastWriter writer;
         std::string compile_string=writer.write(compile_value);  
          //选择负载最低的主机
         while(true)
         {
            int id=0;
            Machine *m=nullptr;
            if(!load_blance_.SmartChoice(&id,&m))
            {
               break;
            }
            
            //发起http请求
            httplib::Client cli(m->ip,m->port);
            m->IncLoad();
            LOG(INFO)<<"选择主机成功,id:"<<id<<" ip:"<<m->ip<<" port:"<<m->port<<"负载:"<<m->Load()<<"\n";
            if(auto res=cli.Post("/compile_run",compile_string,"application/json;charset=utf-8"))
            {
              //结果给out_json
              if(res->status==200)
              {
                *out_json=res->body;
                m->DecLoad();
                LOG(INFO)<<"请求编译运行服务成功!"<<"\n";
                break;
              }
              m->DecLoad();
               
              
            }
            else
            {
              LOG(ERROR)<<"当前请求主机id:"<<id<<" ip:"<<m->ip<<" port:"<<m->port<<"\n";
              load_blance_.OfflineMachine(id);
              load_blance_.ShowMachines();
            }
            

         }
       
       


       }


    };
}